using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Aludra.Core.Models;
using IronPython.Hosting;
using IronPython.Runtime;
using Microsoft.Scripting.Hosting;

namespace Aludra.Core
{
    public class CardsLoader
    {
        private ScriptEngine _engine;

        private string[] _basicKeys;
        private string[] BasicKeys
        {
            get
            {
                if (_basicKeys == null)
                {
                    _basicKeys = new[]
                        {
                            this.Configuration.CardPropertyMapper[CardPropertyKeyConstants.Id],
                            this.Configuration.CardPropertyMapper[CardPropertyKeyConstants.Name],
                            this.Configuration.CardPropertyMapper[CardPropertyKeyConstants.Description],
                            this.Configuration.CardPropertyMapper[CardPropertyKeyConstants.Type]
                        };
                }
                return _basicKeys;
            }
        }

        public string ErrorMessage { get; private set; }

        public CardsLoaderConfiguration Configuration { get; private set; }

        public CardsLoader(CardsLoaderConfiguration config)
        {
            _engine = Python.CreateEngine();
            this.Configuration = config;
        }

        public List<ICard> LoadCardFromScripts(string expression)
        {
            // Clear the error message generated last time
            this.ErrorMessage = string.Empty;
            ScriptScope scope = _engine.CreateScope();
            _engine.Execute(expression, scope);

            // Loop to get all card information from pthon arrays
            IEnumerable<string> allCards = scope.GetVariableNames();
            var cardList = new List<ICard>();
            var errorMessageBuilder = new StringBuilder();
            string keyword = this.Configuration.CardArrayKeyword;

            foreach (string cardScript in allCards)
            {
                // Only look for the card information from the arrays which names contain "card"
                if (cardScript.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) == -1) { continue; }

                // Start to get the basic card property
                dynamic propertyArray = scope.GetVariable(cardScript);
                bool canCreate = BasicKeys.All(key => propertyArray.ContainsKey(key));

                if (!canCreate)
                {
                    errorMessageBuilder.AppendFormat("Some of the card definitions are missing in <{0}>\r\n", cardScript);
                    continue;
                }
                try
                {
                    string id = propertyArray[BasicKeys[0]];
                    string name = propertyArray[BasicKeys[1]];
                    string desc = propertyArray[BasicKeys[2]];
                    var cardType = (CardType)propertyArray[BasicKeys[3]];
                    if (cardType == CardType.Monster)
                    {
                        var newCard = new MonsterCard(id, name, desc);
                        MonsterCardFillInfo(newCard, scope, propertyArray);
						cardList.Add(newCard);
                    }
                    else if (cardType == CardType.Trap || cardType == CardType.Spell)
                    {
                        var newCard = new SpellTrapCard(id, name, desc, cardType);
						SpellTrapCardFillInfo(newCard, scope, propertyArray);
						cardList.Add(newCard);
                    }
                    else
                    {
                        errorMessageBuilder.AppendFormat("Unspecified card type,<{0}>", cardScript);
                    }
                }
                catch (InvalidCastException)
                {
                    errorMessageBuilder.AppendFormat("Some of the card definitions of <{0}> are not vaild\r\n", cardScript);
                }
            }
            this.ErrorMessage = errorMessageBuilder.ToString();
            return cardList;
        }

        private void MonsterCardFillInfo(MonsterCard monster, ScriptScope scope, dynamic propertyArray)
        {
            string executeKey = this.Configuration.CardPropertyMapper[CardPropertyKeyConstants.Execute];
            if (propertyArray.ContainsKey(executeKey))
            {
                Func<object> execute = GetCardExecute(scope, propertyArray[executeKey]);
                if (execute != null)
                {
                    monster.Execute = execute;
                }
            }

            string canExecuteKey = this.Configuration.CardPropertyMapper[CardPropertyKeyConstants.CanExecute];
            if (propertyArray.ContainsKey(canExecuteKey))
            {
                Func<bool> canExecute = GetCardCanExecute(scope, propertyArray[canExecuteKey]);
                if (canExecute != null)
                {
                    monster.CanExecute = canExecute;
                }
            }

            string attackKey = this.Configuration.CardPropertyMapper[CardPropertyKeyConstants.Attack];
            if (propertyArray.ContainsKey(attackKey))
            {
                monster.Attack = Convert.ToInt32(propertyArray[attackKey]);
            }

            string defenseKey = this.Configuration.CardPropertyMapper[CardPropertyKeyConstants.Defense];
            if (propertyArray.ContainsKey(defenseKey))
            {
                monster.Defense = Convert.ToInt32(propertyArray[defenseKey]);
            }

            string levelKey = this.Configuration.CardPropertyMapper[CardPropertyKeyConstants.Level];
            if (propertyArray.ContainsKey(levelKey))
            {
                monster.Level = Convert.ToInt32(propertyArray[levelKey]);
            }

            string rankKey = this.Configuration.CardPropertyMapper[CardPropertyKeyConstants.Rank];
            if (propertyArray.ContainsKey(rankKey))
            {
                monster.Rank = Convert.ToInt32(propertyArray[rankKey]);
            }

            string attributeKey = this.Configuration.CardPropertyMapper[CardPropertyKeyConstants.Attribute];
            if (propertyArray.ContainsKey(attributeKey))
            {
                monster.Attribute = (CardAttribute)propertyArray[attributeKey];
            }

            string moneffectKey = this.Configuration.CardPropertyMapper[CardPropertyKeyConstants.MonsterEffectType];
            if (propertyArray.ContainsKey(moneffectKey))
            {
                monster.MonsterEffectType = (MonsterEffectType)propertyArray[moneffectKey];
            }

            string moncateKey = this.Configuration.CardPropertyMapper[CardPropertyKeyConstants.MonsterCategory];
            if (propertyArray.ContainsKey(moncateKey))
            {
                monster.MonsterCategory = (MonsterCategory)propertyArray[moncateKey];
            }

            string montypeKey = this.Configuration.CardPropertyMapper[CardPropertyKeyConstants.MonsterType];
            if (propertyArray.ContainsKey(montypeKey))
            {
                monster.MonsterType = (MonsterType)propertyArray[montypeKey];
            }
        }
		
		private void SpellTrapCardFillInfo(SpellTrapCard spellTrap, ScriptScope scope, dynamic propertyArray)
		{
			string executeKey = this.Configuration.CardPropertyMapper[CardPropertyKeyConstants.Execute];
            if (propertyArray.ContainsKey(executeKey))
            {
                Func<object> execute = GetCardExecute(scope, propertyArray[executeKey]);
                if (execute != null)
                {
                    spellTrap.Execute = execute;
                }
            }

            string canExecuteKey = this.Configuration.CardPropertyMapper[CardPropertyKeyConstants.CanExecute];
            if (propertyArray.ContainsKey(canExecuteKey))
            {
                Func<bool> canExecute = GetCardCanExecute(scope, propertyArray[canExecuteKey]);
                if (canExecute != null)
                {
                    spellTrap.CanExecute = canExecute;
                }
            }

            string spellTrapTypeKey = this.Configuration.CardPropertyMapper[CardPropertyKeyConstants.SpellTrapType];
            if (propertyArray.ContainsKey(spellTrapTypeKey))
            {
                spellTrap.SpellTrapType = (SpellTrapType)propertyArray[spellTrapTypeKey];
            }         
		}

        private static Func<bool> GetCardCanExecute(ScriptScope scope, string expression)
        {
            Func<bool> canExecute = null;
            string[] funcNames = expression.Split(',');
            if (funcNames.Any(name => scope.TryGetVariable(name, out canExecute)))
            {
                return canExecute;
            }
            return null;
        }

        private static Func<object> GetCardExecute(ScriptScope scope, string expression)
        {
            Func<object> execute = null;
            string[] funcNames = expression.Split(',');
            if (funcNames.Any(name => scope.TryGetVariable(name, out execute)))
            {
                return execute;
            }
            return null;
        }
    }
}
